#!/usr/bin/env python
# coding=utf-8
# Filename : Curve.py
# Created by iFantastic on 2017/7/25
# Description : 曲线类
import numpy as np
import matplotlib.pyplot as plt
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import learning_curve, validation_curve

from sklearn.model_selection import train_test_split
from DataCleaner import get_current_time, DataCleaner
from DataPreprocesser import DataPreprocesser


class Curver_Helper:
    """
    学习曲线和验证曲线的辅助类，用于保存曲线和绘制曲线
    """
    def __init__(self, curve_name, xlabel, x_islog):
        """
        初始化函数

        :param curve_name: 曲线名称
        :param xlabel: X轴的名称
        :param x_islog: X轴是否为对数
        :return:
        """
        self.curve_name = curve_name
        self.xlabel = xlabel
        self.x_islog = x_islog

    def save_curve(self, x_data, train_scores_mean, train_scores_std, test_scores_mean, test_scores_std):
        """
        保存曲线的数据

        :param x_data: 曲线X轴的数据，也就是被考察的指标的序列
        :param train_scores_mean: 训练集预测的平均得分
        :param train_scores_std: 训练集预测得分的标准差
        :param test_scores_mean: 测试集预测的平均得分
        :param test_scores_std: 测试集预测得分的标准差
        :return:
        """
        with open('output/%s' %self.curve_name, 'wb') as output:
            result_array = np.array([x_data, train_scores_mean, train_scores_std, test_scores_mean, test_scores_std])
            np.save(output, result_array)

    def plot_curve(self, x_data, train_scores_mean, train_scores_std, test_scores_mean, test_scores_std):
        """
        绘制并保存图片

        :param x_data: 曲线x轴的数据，也就是被考察的指标的序列
        :param train_scores_mean: 训练集预测的平均得分
        :param train_scores_std: 训练集预测得分的标准差
        :param test_scores_mean: 测试集预测的平均得分
        :param test_scores_std: 测试集预测得分的标准差
        :return:
        """
        min_y1 = np.min(train_scores_mean)
        min_y2 = np.min(test_scores_mean)
        fig = plt.figure(figsize=(20, 15))
        ax = fig.add_subplot(1, 1, 1)
        ax.plot(x_data, train_scores_mean, label='Training roc_auc', color='r', marker='o')
        ax.fill_between(x_data, train_scores_mean-train_scores_std, train_scores_mean+train_scores_std, alpha=0.2, color='r')
        ax.plot(x_data, test_scores_mean, label='Testing roc_auc', color='g', marker='+')
        ax.fill_between(x_data, test_scores_mean-test_scores_std, test_scores_mean+test_scores_std, alpha=0.2, color='g')
        ax.set_title('%s' %self.curve_name)
        ax.set_xlabel('%s' %self.xlabel)
        ax.locator_params(axis='x', tight=True, nbins=10)
        ax.grid(which='both')
        if self.x_islog:ax.set_xscale('log')
        ax.set_ylabel('Score')
        ax.set_ylim(min(min_y1, min_y2)-0.1, 1.1)
        ax.set_xlim(0, max(x_data))
        ax.legend(loc='best')
        ax.grid(True, which='both', axis='both')
        fig.savefig('output/%s.png' %self.curve_name, dpi=100)

    def plot_from_saved_data(self, filename, curve_name, xlabel, x_islog):
        """
        通过保存的数据点来绘制并保存图形

        :param filename: 保存数据点的文件名
        :param curve_name: 曲线名称
        :param xlabel: X轴的名称
        :param x_islog: X轴是否为对数
        :return:
        """
        x_data, train_scores_mean, train_scores_std, test_scores_mean, test_scores_std = np.load(filename)
        helper = Curver_Helper(curve_name, xlabel, x_islog)
        helper.plot_curve(x_data, train_scores_mean, train_scores_std, test_scores_mean, test_scores_std)

def cut_data(data, scale_factor, stratify=True, seed=0):
    """
    切分数据集，使用其中的一部分来学习

    :param data: 原始数据集
    :param scale_factor: 传递给train_test_split的train_size参数，可以为浮点数[0.1, 1]，可以为整数
    :param stratify: 传递给train_test_splot的stratify参数
    :param seed: 传递给train_test_split的seed参数
    :return:
    """
    if stratify:
        return train_test_split(data, train_size=scale_factor, stratify=data[:, -1], random_state=seed)[0]
    else:
        return train_test_split(data, train_size=scale_factor, random_state=seed)[0]


class Curver:
    """
    用于生成学习曲线验证曲线的父类
    """
    def create_curve(self, train_data, curve_name, xlabel, x_islog, scale=0.1, is_gui=False):
        """
        生成曲线

        :param train_data: 训练数据集
        :param curve_name: 曲线名字，用于绘制和保存文件
        :param xlabel: 曲线X轴名字
        :param x_islog: X轴是否为对数坐标
        :param scale: 切分比例，默认使用10%的训练集
        :param is_gui: 是否在GUI环境下。如果在GUI环境下，则绘制图片并保存
        :return:
        """
        class_name = self.__class__.__name__
        self.curve_name = curve_name
        # 加载数据
        data = cut_data(train_data, scale_factor=scale, stratify=True, seed=0)
        self.X = data[:, :-1]
        self.y = data[:, -1]
        # 生成曲线参数
        result = self._curve()
        # 保存和绘制曲线
        self.helper = Curver_Helper(self.curve_name, xlabel, x_islog)
        if(is_gui):self.helper.plot_curve(*result)


class LearningCurver(Curver):
    def __init__(self, train_sizes):
        self.train_sizes = train_sizes
        self.estimator = GradientBoostingClassifier(max_depth=10)

    def _curve(self):
        print('----- Begin run learning_curve(%s) at %s ------' % (self.curve_name, get_current_time()))
        # 获取学习曲线
        abs_trains_sizes,  train_scores, test_scores = learning_curve(self.estimator, self.X, self.y, cv=3, scoring='roc_auc', train_sizes=self.train_sizes, n_jobs=-1, verbose=1)
        print('----- End run learning_curve(%s) at %s ------' % (self.curve_name, get_current_time()))
        # 对于每个test_size，获取3折交叉上预测得分的均值和方差
        train_scores_mean = np.mean(train_scores, axis=1)
        train_scores_std = np.std(train_scores, axis=1)
        test_scores_mean = np.mean(test_scores, axis=1)
        test_scores_std = np.std(test_scores, axis=1)
        return abs_trains_sizes, train_scores_mean, train_scores_std, test_scores_mean, test_scores_std


class ValidationCurver(Curver):
    def __init__(self, param_name, param_range):
        self.p_name = param_name
        self.p_range = param_range
        self.estimator = GradientBoostingClassifier()

    def _curve(self):
        print('----- Begin run validation_curve(%s) at %s ------' %(self.curve_name, get_current_time()))
        train_scores, test_scores = validation_curve(self.estimator, self.X, self.y, param_name=self.p_name, param_range=self.p_range, cv=3, scoring='roc_auc', n_jobs=-1, verbose=1)
        print('----- ENd run validation_curve(%s) at %s ------' %(self.curve_name, get_current_time()))
        train_scores_mean = np.mean(train_scores, axis=1)
        train_scores_std = np.std(train_scores, axis=1)
        test_scores_mean = np.mean(test_scores, axis=1)
        test_scores_std = np.std(test_scores, axis=1)
        return [item for item in self.p_range], train_scores_mean, train_scores_std, test_scores_mean, test_scores_std


def run_learning_curve(data, type_name):
    """
    生成学习曲线

    :param data: 训练集
    :param type_name: 数据种类名
    :return:
    """
    learning_curve = LearningCurver(train_sizes=np.logspace(-1, 0, num=10, endpoint=True, dtype='float'))
    learning_curve.create_curve(data, 'learning_curve_%s' % type_name, xlabel='Nums', x_islog=True, scale=0.99, is_gui=False)


def run_test_subsample(data, type_name, scale, param_range):
    """
    生成验证曲线，验证subsample参数

    :param data: 训练集
    :param type_name: 数据种类名
    :param scale: 样本比例，一个小于1.0的浮点数
    :param param_range: 参数的范围
    :return:
    """
    validation_curve = ValidationCurver('subsample', param_range=param_range)
    validation_curve.create_curve(data, 'validation_curve_subsample_%s' %type_name, xlabel='subsample', x_islog=False, scale=scale, is_gui=False)


def run_test_n_estimators(data, type_name, scale, subsample, param_range):
    """
    生成验证曲线，验证n_estimators参数

    :param data: 训练集
    :param type_name: 数据种类名
    :param scale: 样本比例，一个小于1.0的浮点数
    :param subsample: subsample参数
    :param param_range: n_estimators参数的范围
    :return:
    """
    validation_curve = ValidationCurver('n_estimators', param_range=param_range)
    validation_curve.estimator.set_params(subsample=subsample)  # 调整subsample
    validation_curve.create_curve(data, 'validation_curve_n_estimators_%s' %type_name, xlabel='n_estimators', x_islog=True, scale=scale, is_gui=False)


def run_test_maxdepth(data, type_name, scale, subsample, n_estimators, param_range):
    """
    生成验证曲线，验证maxdepth参数

    :param data: 训练集
    :param type_name: 数据种类名
    :param scale: 样本比例，一个小于1.0的浮点数
    :param subsample: subsample参数
    :param n_estimators: n_estimators参数
    :param param_range: maxdepth参数的范围
    :return:
    """
    validation_curve = ValidationCurver('max_depth', param_range=param_range)
    validation_curve.estimator.set_params(subsample=subsample)  # 调整subsample
    validation_curve.estimator.set_params(n_estimators=n_estimators)  # 调整n_estimators
    validation_curve.create_curve(data, 'validation_curve_maxdepth_%s' %type_name, xlabel='maxdepth', x_islog=True, scale=scale, is_gui=False)


if __name__ == '__main__':
    clearner = DataCleaner('./input/people.csv', './input/act_train.csv', './input/act_test.csv')
    result = clearner.load_data()
    preprocessor = DataPreprocesser(*result)
    train_datas, test_datas = preprocessor.load_data()

    run_learning_curve(train_datas['type 7'], 'type7')
    # helper = Curver_Helper('learning_curve_type7', 'Nums', True)
    # helper.plot_from_saved_data('output/processed_data', 'learning_curve_type7', 'Nums', True)